BatchWriteItemを利用してAmazon DynamoDBのテーブルを空にする
こんにちは。サービスグループの武田です。
DynamoDBを使用しているシステムのテストや動作検証をしていると、一旦テーブルの内容をクリアしたいことがあるかと思います。
DynamoDBのテーブルを削除して再作成ができれば早いんですが、今回は以下のようなケースで再作成が難しい状況でした。
- CloudFormationで作成したリソースのため手動での再作成は避けたかった
- テーブル名の一部にランダム値が入りスタックの再作成はやりたくなかった
- 別アプリケーションの設定ファイルにテーブル名がハードコーディングされていた
3. 本番環境構築にも使用しているテンプレートで 正直触りたくなかった 変更が難しかった
さて、デーブルの再作成ができないとなると、データを一つ一つ消していくという手段になります。
実はAWS コンソールでは100件ずつではありますが、一括削除することができます。
最初はこの方法で削除してたんですが、データを削除したいテーブルが複数個あったのも災いして、何回か繰り返しているうちにめんどくさくなってきてしまい、楽したいなと思い始めました。
というわけで以下の要求を解決するべくスクリプト作成に取りかかりました。
- ブラウザに切り替えずに削除したい
- 100件以上でも一発で削除したい
- 複数テーブルのデータを楽に削除したい
- 楽したい
動作環境
動作確認は以下の環境で行なっています。
- macOS Sierra
- AWS CLI
- jq
$ sw_vers -productVersion 10.12.6 $ aws --version aws-cli/1.11.180 Python/3.6.3 Darwin/16.7.0 botocore/1.7.38 $ jq --version jq-1.5
削除スクリプトの作成
DynamoDBではDeleteItemという操作でデータを一つ削除することができます。つまり全データを削除するためには、データ一つ一つに対してDeleteItemを実行すれば良いことになります。
……なんですが、実はBatchWriteItemという操作を使うと25件ずつまとめて削除することができます。バッチ操作を利用することでネットワークラウンドトリップを減らすことができ、さらにDynamoDB側で並行して書き込み処理をしてくれて大変便利です。
初めはDeleteItemを使ったスクリプトを書いたんですが、「BatchWriteItem使った方がきっとパフォーマンス良くなるよ!」というアドバイスをもらい書き換えました。
ということでこんなスクリプトになりました。
dynamodb_truncate.sh
#!/bin/bash set -e prefix=$1 tables=$(aws dynamodb list-tables | jq -r '.[][] | select(. | test("^'$prefix'-.+"))') tmp_scan_data=$(mktemp) tmp_delete_items=$(mktemp) for t in $tables; do key=$(aws dynamodb describe-table --table-name $t | jq -r '.Table.KeySchema[].AttributeName') # 予約語はprojection-expressionで指定できないため、全て#を付与してしまう proj=$(echo -n $key | tr ' ' '\n' | sed -E 's/(.+)/#\1/' | tr '\n' ',') attr=$(echo -n $key | tr ' ' '\n' | sed -E 's/(.+)/"#\1":"\1"/' | tr '\n' ',' | sed -E 's/(.+)/{\1}/') while :; do aws dynamodb scan --table-name $t --projection-expression "$proj" --expression-attribute-names "$attr" --max-item 25 > $tmp_scan_data count=$(cat $tmp_scan_data | jq '.Count') if [[ $count -eq 0 ]]; then echo "${t}: delete completed." break fi echo "${t}: delete progress ... ${count}." cat $tmp_scan_data | jq '.Items[] | {"Key": .} | {"DeleteRequest": .}' | jq -s '.' | jq '{"'$t'": .}' > $tmp_delete_items aws dynamodb batch-write-item --request-items file://$tmp_delete_items > /dev/null done done
以下のように実行します。
./dynamodb_truncate.sh hoge
そうするとhoge-*
のテーブル全てのデータを削除することができます。
これで手軽にテーブルデータを空にすることができるようになりました。
作成中に詰まったポイント
スクリプトを作成する中で詰まったポイントを紹介します。
キー以外の情報を含めてしまう
DeleteItemでもBatchWriteItemでも同様なんですが、削除するデータを指定する際にはキー情報のみを指定します。
そうしないと
An error occurred (ValidationException) when calling the BatchWriteItem operation: The number of conditions on the keys is invalid
というエラーが出て怒られます。
スクリプトでは、scanする際に--projection-expression
オプションを指定することでキーの値のみを取得するようにしています。
aws dynamodb scan --table-name $t --projection-expression "$proj" --expression-attribute-names "$attr" --max-item 25 > $tmp_scan_data
テーブルによってキーが違う
キー情報のみを取り出すためには、キー名を指定する必要があります。ところが、当たり前ですがテーブルによってプライマリキーは異なります。そのためスクリプト内でキー名を指定することができません。
メタ情報から取ってくればいけるかな?ということで、スクリプトではテーブルのメタ情報からキー名を取得するようにしています。
またプライマリキーの数も1つのテーブルと2つのテーブルがあることに注意が必要でした。
key=$(aws dynamodb describe-table --table-name $t | jq -r '.Table.KeySchema[].AttributeName')
キー名に予約語を使っている
--projection-expression
オプションでキー名のみを指定すると前述しましたが、DynamoDBの予約語を直接指定すると以下のようなエラーが返されます(予約語timestamp
を使っていた場合)。
An error occurred (ValidationException) when calling the Scan operation: Invalid ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: timestamp
対応策としては、--projection-expression
には#
から始まるプレースホルダーを指定し、プレースホルダーと属性名のマッピング情報を--expression-attribute-names
で指定してあげます。
スクリプトではキー名に一律で#
を付与することでエラーが起きないようにしています。
proj=$(echo -n $key | tr ' ' '\n' | sed -E 's/(.+)/#\1/' | tr '\n' ',') attr=$(echo -n $key | tr ' ' '\n' | sed -E 's/(.+)/"#\1":"\1"/' | tr '\n' ',' | sed -E 's/(.+)/{\1}/') ... aws dynamodb scan --table-name $t --projection-expression "$proj" --expression-attribute-names "$attr" --max-item 25 > $tmp_scan_data
まとめ
DynamoDBではテーブルのデータを空にしたい場合、まず考えるべきことはテーブルの再作成です。ところが諸々の事情で、テーブルの再作成をせず、データだけ消したいということもあると思います。そのような場合に少しでも役立てられたら嬉しいです。
最後に注意点ですが、Scanは読み込みキャパシティーユニット(RCU)を、BatchWriteItemは書き込みキャパシティーユニット(WCU)を消費します。そのため、上記のスクリプトによる処理速度はテーブルに設定されているスループットキャパシティーと実際に削除するデータ量に依存します。
RCUとWCUが1に指定されているテーブルでデータ削除の検証をしてみたところ、100件の削除に10秒程度、1000件の削除に12分弱かかりました。数千レコード程度のオーダーであれば大丈夫そうですね(ちなみにDeleteItemバージョンでは100件の削除に1分30秒ほどかかっていました)。
一方で、データ量が多く削除時間が無視できない場合には、テーブル再作成できないかを考慮した方が良さそうです。
それでは、よいDynamoライフを!!